C++ allows the traditional C-style casts [E.G. (int) f
] and functional notation casts [E.G. int(f)
], but adds its own
forms:
-
static_cast<type>(expression)
-
const_cast<type>(expression)
-
dynamic_cast<type>(expression)
-
reinterpret_cast<type>(expression)
-
std::bit_cast<type>(expression)
(since C++20)
C-style casts and functional notation casts are largely functionally equivalent. However, when they do not invoke a converting constructor, C-style
casts are capable of performing dangerous conversions between unrelated types and of changing a variable’s const
-ness. Attempt to do
these things with an explicit C++-style cast, and the compiler will catch the error. Use a C-style or functional notation cast, and it cannot.
Moreover, C++20 has introduced a std::bit_cast
as a way of reinterpreting a value as being of a different type of the same length
preserving its binary representation. The behavior of such conversion when performed via C-style cast or reinterpret_cast
is
undefined.
Additionally, C++-style casts are preferred because they are visually striking. The visual subtlety of a C-style or functional cast may mask that a
cast has taken place, but a C++-style cast draws attention to itself, and makes the the programmer’s intention explicit.
This rule raises an issue when C-style cast or functional notation cast is used.
Noncompliant code example
#include <iostream>
class Base { };
class Derived: public Base
{
public:
int a;
};
void DoSomethingElse(Derived *ptr)
{
ptr->a = 42;
}
void DoSomething(const Base *ptr)
{
Derived* derived = (Derived*)ptr; // Noncompliant; inadvertently removes constness
DoSomethingElse(derived);
}
void checksBits(float f)
{
int x = *(int*)&f; // Noncompliant; has undefined behavior
}
int main(int argc, char* argv[])
{
Derived *ptr = new Derived();
ptr->a = 1337;
DoSomething(ptr);
std::cout << ptr->a << std::endl; /* 1337 was expected, but 42 is printed */
return 0;
}
Compliant solution
/* ... */
void DoSomething(const Base *ptr)
{
/* error: static_cast from type 'const Base*' to type 'Derived*' casts away qualifiers */
Derived* derived = static_cast<Derived*>(ptr); // Compliant. Compile fails with above error
DoSomethingElse(derived);
}
void checksBits(float f)
{
int x = std::bit_cast<int>(f);
}
/* ... */
Exceptions
This rule ignores void casts. They are a common pattern to express that an expression is dropped on purpose.
(void)call(); // Compliant by exception
void(call()); // Compliant by exception
Furthermore, this rule ignores functional casts when creating an object of the same type (copy) or when they lead to calls to a constructor. In
those cases, the purpose is clear and no dangerous conversion can happen.
class Clazz {
Clazz(int, int);
};
Clazz create();
template<typename T>
void process(T&& t);
void foo(int i, Clazz c) {
process(Clazz(1, 2)); // Compliant: constructor call
process(Clazz(c)); // Compliant: copy
process(auto(c)); // Compliant: copy
process(Clazz(create())); // Compliant: copy
process((Clazz)c); // Noncompliant: C-Style cast
process(int(i)); // Compliant: copy
process(auto(i)); // Compliant: copy
process((int)i); // Noncompliant: C-style cast
}